/*
 * (C) Copyright 2006-2010 Nuxeo SAS (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     bstefanescu
 */
package org.nuxeo.gwt.habyt.invocation.client;

import java.io.Serializable;

/**
 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
 * 
 */
@SuppressWarnings("serial")
public class ObjectRef implements Referenceable, Serializable {

    protected String type;

    protected String id;

    protected String modifier;

    protected Parameters params;

    private int hash = 0;

    /**
     * Needed by GWT serialization
     */
    protected ObjectRef() {
    }

    public ObjectRef(String ref) {
        if (ref == null) {
            throw new IllegalArgumentException("Argument ref must be not null");
        }
        int i = ref.lastIndexOf('?');
        if (i > -1) {
            params = new Parameters(ref.substring(i + 1));
            ref = ref.substring(0, i);
        }
        i = ref.indexOf(':');
        if (i > -1) {
            type = ref.substring(0, i);
            id = ref.substring(i + 1);
            i = id.indexOf(':');
            if (i > -1) {
                modifier = id.substring(i + 1);
                id = id.substring(0, i);
            }
        } else {
            throw new IllegalArgumentException(
                    "Invalid object ref specification: " + ref);
        }
    }

    public ObjectRef(String type, String id) {
        this(type, id, null);
    }

    public ObjectRef(String type, String id, String modifier) {
        if (id == null || type == null) {
            throw new IllegalArgumentException("id and type must not be null");
        }
        this.type = type;
        this.id = id;
        this.modifier = modifier;
    }

    public Parameters getParameters() {
        return params;
    }

    public void setParameters(Parameters params) {
        this.params = params;
    }

    public void setParameter(String key, String value) {
        if (value == null) {
            if (params != null) {
                params.remove(key);
            }
        } else {
            if (params == null) {
                params = new Parameters();
            }
            params.put(key, value);
        }
    }

    public String getParameter(String key) {
        return params != null ? params.get(key) : null;
    }

    public final String getType() {
        return type;
    }

    public final String getId() {
        return id;
    }

    public final String getModifier() {
        return modifier;
    }

    public final boolean hasModifier() {
        return modifier != null;
    }

    public final boolean hasParameters() {
        return params != null && !params.isEmpty();
    }

    @Override
    public final ObjectRef getRef() {
        return this;
    }

    public final boolean hasSameType(Referenceable ref) {
        return ref != null ? type.equals(ref.getRef().getType()) : false;
    }

    public final boolean hasSameId(Referenceable ref) {
        return ref != null ? id.equals(ref.getRef().getId()) : false;
    }

    public final boolean hasSameModifier(Referenceable ref) {
        if (ref == null) {
            return false;
        }
        ObjectRef oref = ref.getRef();
        return (modifier != null ? modifier.equals(oref.modifier)
                : modifier == oref.modifier);
    }

    public final boolean hasSameBase(Referenceable ref) {
        if (ref == null) {
            return false;
        }
        ObjectRef oref = ref.getRef();
        return type.equals(oref.type) && id.equals(oref.id);
    }

    public final boolean sameAs(Referenceable ref) {
        if (ref == null) {
            return false;
        }
        ObjectRef oref = ref.getRef();
        return type.equals(oref.type)
                && id.equals(oref.id)
                && (modifier != null ? modifier.equals(oref.modifier)
                        : modifier == oref.modifier);
    }

    public final ObjectRef getBaseRef() {
        return modifier == null ? this : new ObjectRef(type, id, null);
    }

    public final Invocation getInvocation(String action) {
        return new Invocation(this, action);
    }

    public final Invocation getInvocation(String action, Serializable content) {
        return new Invocation(this, action, content);
    }

    @Override
    public final boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof ObjectRef) {
            ObjectRef ref = (ObjectRef) obj;
            if (type.equals(ref.getType()) && id.equals(ref.getId())) {
                return modifier != null ? modifier.equals(ref.modifier)
                        : modifier == ref.modifier;
            }
        }
        return false;
    }

    @Override
    public final int hashCode() {
        if (hash == 0) {
            hash = type.hashCode() ^ id.hashCode();
            if (modifier != null) {
                hash = hash ^ modifier.hashCode();
            }
        }
        return hash;
    }

    /**
     * An unique string representation of this object reference. String
     * references can be used as a parameter in constructor to create a new
     * {@link ObjectRef}
     */
    @Override
    public final String toString() {
        StringBuilder buf = new StringBuilder(44);
        buf.append(type).append(':').append(id);
        if (modifier != null) {
            buf.append(':').append(modifier);
        }
        if (params != null && !params.isEmpty()) {
            buf.append('?').append(params.toString());
        }
        return buf.toString();
    }

}
